//
//  AppDelegate.m
//  Statz
//
//  Created by Dave MacLachlan on 07/08/03.
//
// Copyright 2007 Google Inc.
// 
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.
//

#import <Carbon/Carbon.h>
#import "AppDelegate.h"
#import "StatzProtocols.h"
#import "StartUpWindowController.h"
#import "Status.h"
#import "StatusSettingsController.h"
#import "StatzSink.h"
#import "UserStatusSource.h"
#import "StatusChangedAnimation.h"
#import "StatusItemView.h"
#import "StatusUpdate.h"
#import "StatzApplescriptPlugin.h"

static NSString * const kStatzDataKey = @"StatzData";
NSString * const StatzPreferencesLoadedNotification = @"StatzPreferencesLoadedNotification";
							 
@implementation AppDelegate
- (id)init {
  self = [super init];
  if (self != nil) {
    statusSources_ = [[NSMutableArray array] retain];
    plugins_ = [[NSMutableArray array] retain];
  }
  return self;
}

- (void)dealloc {
  [statusSources_ release];
  [plugins_ release];
  [statusSettingsController_ release];
  [super dealloc];
}

- (void)applicationWillFinishLaunching:(NSNotification *)notification {
  NSFileManager *defaultManager = [NSFileManager defaultManager];
  
  NSString *const statzFolderName = @"Statz";
  NSString *const plugInFolderName = @"Plug-Ins";
  
  // Make the user's Statz Support dirs if they don't exist
  NSArray *userSupportDirs = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  if (userSupportDirs && [userSupportDirs count] == 1) {
    NSString *userSupportDir = [[userSupportDirs objectAtIndex:0] stringByAppendingPathComponent:statzFolderName];
    if (![defaultManager fileExistsAtPath:userSupportDir]) {
      [defaultManager createDirectoryAtPath:userSupportDir attributes:nil];
    }
    userSupportDir = [userSupportDir stringByAppendingPathComponent:plugInFolderName];
    if (![defaultManager fileExistsAtPath:userSupportDir]) {
      [defaultManager createDirectoryAtPath:userSupportDir attributes:nil];
    }
  }

  // Get my plugin directories
  NSArray *appSupportDirs = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSAllDomainsMask, YES);
  
  NSMutableArray *statzPluginDirs = [NSMutableArray arrayWithCapacity:[appSupportDirs count] + 1];
  NSEnumerator *appSupportEnumerator = [appSupportDirs objectEnumerator];
  NSString *path = [[NSBundle mainBundle] builtInPlugInsPath];
  [statzPluginDirs addObject:path];
  while ((path = [appSupportEnumerator nextObject])) {
    NSString *fullPath = [[path stringByAppendingPathComponent:statzFolderName] stringByAppendingPathComponent:plugInFolderName];
    [statzPluginDirs addObject:fullPath];
  }
  
  // Find plugins
  NSString *pluginRootDir;
  NSEnumerator *pluginRootDirEnumerator = [statzPluginDirs objectEnumerator];
  while ((pluginRootDir = [pluginRootDirEnumerator nextObject])) {
    NSDirectoryEnumerator *pluginEnumerator = [defaultManager enumeratorAtPath:pluginRootDir];
    if (!pluginEnumerator) continue;
    NSString *pluginPath;
    while ((pluginPath = [pluginEnumerator nextObject])) {
      NSDictionary *attribs = [pluginEnumerator fileAttributes];
      if ([[attribs objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]) {
        [pluginEnumerator skipDescendents];
      }
      id<StatzSinkPlugin> plugin = nil;
      NSString *fullPath = [pluginRootDir stringByAppendingPathComponent:pluginPath];
      if ([fullPath hasSuffix:@".statzPlugIn"]) {
        NSBundle *pluginBundle = [NSBundle bundleWithPath:fullPath];
        if (pluginBundle) {
          NSString *pluginType = [pluginBundle objectForInfoDictionaryKey:@"StatzPluginType"];
          if (pluginType) {
            Class pluginClass = [pluginBundle principalClass];
            if (pluginClass) {
              plugin = [[pluginClass alloc] init];
            } 
          }
        }
        if (!plugin) {
          NSLog(@"Unable to create plugin from %@", pluginPath);
        }
      } else if ([fullPath hasSuffix:@".scpt"] || [fullPath hasSuffix:@".scptd"]) {
        plugin = [[StatzApplescriptPlugin alloc] initWithApplescript:fullPath];
      } else if ([fullPath hasSuffix:@".app"]) {
        NSBundle *pluginBundle = [NSBundle bundleWithPath:fullPath];
        if (pluginBundle) {
          NSString *appScriptName = [pluginBundle objectForInfoDictionaryKey:@"StatzScript"];
          if (appScriptName) {
            NSString *extension = [appScriptName pathExtension];
            NSString *name = [[appScriptName lastPathComponent] stringByDeletingPathExtension];
            NSString *path = [appScriptName stringByDeletingLastPathComponent];
            NSString *appScriptPath = [pluginBundle pathForResource:name ofType:extension inDirectory:path];
            if (appScriptPath) {
              plugin = [[StatzApplescriptPlugin alloc] initWithApplescript:appScriptPath];
            }
          }
        }
      }
      if (plugin) {
        [plugins_ addObject:plugin];
        [plugin release];
      }
    }
  }
  
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];  
  NSData *data = [defaults objectForKey:kStatzDataKey];
  if (data) {
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    [statusSources_ addObjectsFromArray:[unarchiver decodeObjectForKey:kStatzDataKey]];
    [unarchiver finishDecoding];
    [unarchiver release];
    
  }
  if ([[self statusSourcesWithoutSinks] count] == 0) {
    DefaultStatusSource *defaultSource = [[[DefaultStatusSource alloc] init] autorelease];
    [statusSources_ addObject:defaultSource];
  }    
  
  NSMutableArray *unclaimedPlugins = [[plugins_ mutableCopy] autorelease];
  NSEnumerator *sinkEnum = [[[self statusSinks] copy] objectEnumerator];
  StatzSink *sink = nil;
  while ((sink = [sinkEnum nextObject])) {
    id<StatzSinkPlugin> plugin = [sink plugin];
    if (!plugin) {
      [statusSources_ removeObject:sink];
    }
    [unclaimedPlugins removeObject:plugin];
  }
  
  NSEnumerator *unclaimedPluginsEnum = [unclaimedPlugins objectEnumerator];
  id<StatzSinkPlugin> plugin = nil;
  while ((plugin = [unclaimedPluginsEnum nextObject])) {
    NSString *appBundleID = [plugin appBundleID];
    BOOL isInstalled = YES;
    if (appBundleID && 
        ![[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:appBundleID]) {
      isInstalled = NO;
    }
    if (isInstalled) {
      StatzSource *source = [[[StatzSink alloc] initWithPlugin:plugin] autorelease];
      if (source) {
        [statusSources_ addObject:source];
      }
    }
  }
  
  if (!data || ((GetCurrentKeyModifiers() & 0xFFFF) == optionKey)) {
    if (!data) {
      [self handleFirstLaunch];
    }
    [self showStartupWindow:self];
  }
      
  // Insert Status Item
  NSStatusBar *bar = [NSStatusBar systemStatusBar];
  statusItem_ = [[bar statusItemWithLength:GetMBarHeight()] retain];
  StatusItemView *view = [[[StatusItemView alloc] initWithStatusItem:statusItem_] autorelease];
  [view setImage:[NSImage imageNamed:@"menuIcon"]];
  [view setImage:[NSImage imageNamed:@"menuIcon"]];
  [view setAlternateImage:[NSImage imageNamed:@"menuIconInverted"]];
  [view setAction:@selector(showSettings:)];
  [view setTarget:self];
  [statusItem_ setView:view];
  
  statusSettingsController_ = [[StatusSettingsController alloc] initWithDelegate:self];
  NSWindow *window = [statusSettingsController_ window];
  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  [nc addObserver:self selector:@selector(applyStatusesWithNotification:) 
             name:NSWindowDidResignKeyNotification object:window];
}

- (void)applicationWillTerminate:(NSNotification*)n {  
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  NSMutableData *data = [NSMutableData data];
  NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
  [archiver encodeObject:[self statusSources] forKey:kStatzDataKey];
  [archiver finishEncoding];
  [archiver release];
  [defaults setObject:data forKey:kStatzDataKey];  
  [statusItem_ release];
}

  
- (void)jumpTo:(id)sender {
  id<StatzSinkPlugin> sink = [sender representedObject];
  NSString *bundleID = [sink appBundleID];
  NSWorkspace *ws = [NSWorkspace sharedWorkspace];
  if (bundleID) {
    [ws launchAppWithBundleIdentifier:bundleID
                              options:NSWorkspaceLaunchDefault
       additionalEventParamDescriptor:nil
                     launchIdentifier:nil];
  } else {
    NSString *server = [sink serverName];
    if (server) {
      server = [@"http://" stringByAppendingString:server];
      [ws openURL:[NSURL URLWithString:server]];
    }
  }
}

- (IBAction)showSettings:(id)sender {  
  if ([[statusSettingsController_ window] isKeyWindow]) {
    [statusSettingsController_ close];
  } else {
    StatusItemView *view = (StatusItemView*)[statusItem_ view];
    [view setSelected:YES];
    [statusSettingsController_ showWindow:sender];
  }
}

- (void)handleFirstLaunch {
  NSArray *iChatPrefsLoc = [NSArray arrayWithObjects:NSHomeDirectory(), @"Library", @"Preferences", @"com.apple.iChat.plist", nil];
  NSString *iChatPrefsFile = [NSString pathWithComponents:iChatPrefsLoc];
  NSDictionary *iChatPrefs = [NSDictionary dictionaryWithContentsOfFile:iChatPrefsFile];
  if (iChatPrefs) {
    NSArray *availableMessages = [iChatPrefs objectForKey:@"CustomAvailableMessages"];
    NSEnumerator *messageEnum = [availableMessages objectEnumerator];
    NSString *message;
    StatzSource *defaultSource = [self defaultStatusSource];
    while ((message = [messageEnum nextObject])) {
      [defaultSource addStatus:[Status statusWithString:message]];
    }
    NSArray *awayMessages = [iChatPrefs objectForKey:@"CustomAwayMessages"];
    messageEnum = [awayMessages objectEnumerator];
    while ((message = [messageEnum nextObject])) {
      [defaultSource addStatus:[Status statusWithString:message]];
    }
  }
  NSEnumerator *sinkEnumerator = [[self statusSinks] objectEnumerator];
  StatzSink *sink = nil;
  while ((sink = [sinkEnumerator nextObject])) {
    id<StatzSinkPlugin> sinkPlugin = [sink plugin];
    if ([sinkPlugin appBundleID] || [sinkPlugin isKindOfClass:[StatzApplescriptPlugin class]]) {
      [sink setEnabled:YES];
    }
  }
}

- (IBAction)showStartupWindow:(id)sender {
  StartUpWindowController *startupWindow = [[StartUpWindowController alloc] initWithDelegate:self];
  [startupWindow showWindow:self];
  [NSApp activateIgnoringOtherApps:YES];
}

- (NSArray*)enabledStatusSinks {
  NSMutableArray *sinks = [NSMutableArray array];
  NSEnumerator *sourceEnum = [statusSources_ objectEnumerator];
  id source;
  while ((source = [sourceEnum nextObject])) {
    if ([source isKindOfClass:[StatzSink class]] 
        && [(StatzSink*)source isEnabled]) {
      [sinks addObject:source];
    }
  }
  return sinks;
}  

- (NSArray*)statusSinks {
  NSMutableArray *sinks = [NSMutableArray array];
  NSEnumerator *sourceEnum = [statusSources_ objectEnumerator];
  id source;
  while ((source = [sourceEnum nextObject])) {
    if ([source isKindOfClass:[StatzSink class]]) {
      [sinks addObject:source];
    }
  }
  return sinks;
}

- (NSArray*)statusSourcesWithoutSinks {
  NSMutableArray *sources = [NSMutableArray array];
  NSEnumerator *sourceEnum = [statusSources_ objectEnumerator];
  id source;
  while ((source = [sourceEnum nextObject])) {
    if (![source isKindOfClass:[StatzSink class]]) {
      [sources addObject:source];
    }
  }
  return sources;
}

- (StatzSource*)defaultStatusSource {
  return [statusSources_ objectAtIndex:0];
}

- (NSArray*)statusVariables {
  return [[self statusSourcesWithoutSinks] arrayByAddingObjectsFromArray:[self enabledStatusSinks]];
}

- (NSArray*)statusSources {
  return [[statusSources_ retain] autorelease];
}

- (NSArray*)plugins {
  return [[plugins_ retain] autorelease];
}

- (IBAction)applyStatuses:(id)sender {
  StatusItemView *view = (StatusItemView*)[statusItem_ view];
  [view setSelected:NO];
  NSArray *statusSinks = [self statusSinks];
  NSEnumerator *sinkEnumerator = [statusSinks objectEnumerator];
  StatzSink<StatzSinkPlugin> *sink;
  NSArray *runningApps = [[NSWorkspace sharedWorkspace] launchedApplications];
  NSEnumerator *appEnumerator = [runningApps objectEnumerator];
  NSDictionary *dict;
  NSMutableArray *appBundleIDs = [NSMutableArray arrayWithCapacity:[runningApps count]];
  while ((dict = [appEnumerator nextObject])) {
    NSString *bundleID = [dict objectForKey:@"NSApplicationBundleIdentifier"];
    [appBundleIDs addObject:bundleID];
  }
  while ((sink = [sinkEnumerator nextObject])) {
    if ([sink isEnabled]) {
      NSString *bundleID = [sink appBundleID];
      if (!bundleID || [appBundleIDs containsObject:bundleID]) {
        Status *currentStatus = [sink currentStatus];
        if (currentStatus) {
          StatusUpdate *update = [StatusUpdate statusUpdateWithString:[currentStatus status]
                                                                state:[sink state]];
          [NSThread detachNewThreadSelector:@selector(updateStatusThread:)
                                   toTarget:sink
                                 withObject:update];
        }
      }
    }
  }
}

- (void)applyStatusesWithNotification:(NSNotification *)notification {
  [self applyStatuses:[notification object]];
}  

- (NSString *)description {
  return [NSString stringWithFormat:@"AppDelegate sources: %@\nEnabled Sinks:%@\nAll Sinks:%@", [self statusSourcesWithoutSinks], [self enabledStatusSinks], [self statusSinks]];
}

- (void)statusUpdatedForSink:(id<StatzSinkPlugin, NSObject>)plugin {
  StatusChangedAnimation *animation = [[StatusChangedAnimation alloc] initWithImage:[plugin image] 
                                                                         statusItem:statusItem_];
  [animation setDelegate:self];
  
  if (lastAnimation_) {
    [animation startWhenAnimation:lastAnimation_ reachesProgress:1.0f];
    lastAnimation_ = animation;
  } else {
    if (recoverAnimation_) {
      [recoverAnimation_ stopAnimation];
      [recoverAnimation_ release];
      recoverAnimation_ = nil;
    }
    NSImage *statzIcon = [NSImage imageNamed:@"menuIcon"];
    StatusChangedAnimation *startUpAnimation = [[StatusChangedAnimation alloc] initWithImage:statzIcon 
                                                                                  statusItem:statusItem_];
    [startUpAnimation setCurrentProgress:0.5f];
    [startUpAnimation setDelegate:self];
    [animation startWhenAnimation:startUpAnimation reachesProgress:1.0f];
    lastAnimation_ = animation;
    [startUpAnimation startAnimation];
  }
}

- (void)animationDidEnd:(NSAnimation *)animation {
  if (animation == lastAnimation_) {
    NSImage *statzIcon = [NSImage imageNamed:@"menuIcon"];
    recoverAnimation_ = [[StatusChangedAnimation alloc] initWithImage:statzIcon 
                                                           statusItem:statusItem_];
    [recoverAnimation_ setDelegate:self];
    [recoverAnimation_ addProgressMark:0.5f];
    [recoverAnimation_ startAnimation];
    lastAnimation_ = nil;
  }
  [animation release];
}

- (void)animation:(NSAnimation*)animation didReachProgressMark:(NSAnimationProgress)progress {
  if (animation == recoverAnimation_) {
    if (progress >= 0.5f) {
      [animation stopAnimation];
      [animation release];
      recoverAnimation_ = nil;
      NSImage *statzIcon = [NSImage imageNamed:@"menuIcon"];
      [statusItem_ setImage:statzIcon];
    }
  }
}

- (NSPoint)leftOfStatusItem {
  NSView *statusItemView = [statusItem_ view];
  NSPoint origin = [statusItemView convertPoint:[statusItemView frame].origin toView:nil];
  NSPoint windowOrigin = [[statusItemView window] frame].origin;
  windowOrigin.x += origin.x;
  windowOrigin.y += origin.y;
  return windowOrigin;
}

@end

